using System;
using System.Collections;
using System.Collections.Specialized;
using System.Drawing;
using System.Globalization;
using System.Xml;
using System.Collections.Generic;

namespace FreeFlow.Client
{
  /// <summary>Alignment of text in a label or text field.</summary>
  public enum Align
  {
    /// <summary>Aligned to left.</summary>
    Left, 
    /// <summary>Aligned to right.</summary>
    Right, 
    /// <summary>Center aligned.</summary>
    Center
  }

	/// <summary>
	/// A field on a form.
	/// </summary>
  [Serializable]
  public class Field : Connectable
  {
    internal Field(Form form, XmlElement fieldNode)
    {
      this.form = form;
      if (fieldNode != null)
      {
        if (fieldNode.Attributes["Height"] != null)
          height = int.Parse(fieldNode.Attributes["Height"].Value);
        if (fieldNode.Attributes["Width"] != null)
          width = int.Parse(fieldNode.Attributes["Width"].Value);
        top = int.Parse(fieldNode.GetAttribute("Top"));
        left = int.Parse(fieldNode.GetAttribute("Left"));
        name = fieldNode.GetAttribute("Field");
        hint = fieldNode.GetAttribute("Hint");
        usage = FieldUsage.Optional;
        autoRefill = fieldNode.GetAttribute("AutoRefill") == "1";

        XmlElement visibilityNode = fieldNode["Visibility"];
        if (visibilityNode != null)
        {
          visibilityScript = visibilityNode.InnerText;
        }

        // client scripts
        eventList = new ClientScriptEvents(fieldNode["EventList"]);

        // client extensions
        XmlElement extensions = fieldNode["ClientExtensions"];
        if (extensions != null)
        {
          clientExtensions = extensions.InnerText;
        }
      }
    }

    internal virtual bool ShouldAddToFieldInput()
    {
      return true;
    }

    internal virtual void FillValue(XmlElement node)
    {
      XmlElement valNode = node["Value"];
      if (valNode != null)
        Value = valNode.InnerText;
    }

    internal static FieldUsage UsageFromString(string usage)
    {
      if (usage == "Required")
        return FieldUsage.Required;
      else if (usage == "ReadOnly")
        return FieldUsage.ReadOnly;
      else if (usage == "Hidden")
        return FieldUsage.Hidden;
      else
        return FieldUsage.Optional;
    }

    internal void FillUsage(XmlElement fieldUsageNode)
    {
      string usageString = fieldUsageNode.Attributes["Usage"].Value;
      usage = UsageFromString(usageString);
    }

    /// <summary>
    /// Gets the validation message.
    /// </summary>
    /// <returns>Returns null if the field has no validation errors, an error message otherwise.</returns>
    public virtual string GetValidationMessage()
    {
      if (Usage == FieldUsage.Required)
      {
        if (string.IsNullOrEmpty(Value))
          return FieldMustBeCompleted;
      }

      return null;
    }

    internal void Validate()
    {
      if ((Usage != FieldUsage.Hidden) && (Usage != FieldUsage.ReadOnly))
      {
        string error = GetValidationMessage();
        if (error != null)
          throw new FreeFlowException("Field '" + Name + "' failed validation - " + error);
      }
    }

    internal const string FieldMustBeCompleted = "The field must be completed";

    #region helper functions
    /// <summary>
    /// Converts a color string returned from the engine to a .NET Color.
    /// </summary>
    /// <param name="color">Color string from engine.</param>
    public static Color ParseColor(string color)
    {
      if ((color == null) || (color.Length < 6))
        throw new ArgumentException("color must have a length of 6 characters", "color");
      
      int red = int.Parse(color.Substring(0, 2), NumberStyles.HexNumber);
      int green = int.Parse(color.Substring(2, 2), NumberStyles.HexNumber);
      int blue = int.Parse(color.Substring(4, 2), NumberStyles.HexNumber);
      return Color.FromArgb(red, green, blue);
    }

    /// <summary>
    /// Parses the specified font XML node.
    /// </summary>
    /// <param name="fontNode">Font node.</param>
    public static Font ParseFont(XmlElement fontNode)
    {
      if (fontNode != null)
      {
        string fontName = fontNode.GetAttribute("Name");
        int fontSize = int.Parse(fontNode.GetAttribute("Size"));
        
        // italic, bold, underline, strikethrough
        string fontAttributes = fontNode.GetAttribute("Attributes");
        FontStyle style = new FontStyle();
        if (fontAttributes.IndexOf("I")  > -1)
          style = style | FontStyle.Italic;
        if (fontAttributes.IndexOf("B")  > -1)
          style = style | FontStyle.Bold;
        if (fontAttributes.IndexOf("S")  > -1)
          style = style | FontStyle.Strikeout;
        if (fontAttributes.IndexOf("U")  > -1)
          style = style | FontStyle.Underline;

        Font font = new Font(fontName, fontSize, style, GraphicsUnit.Pixel);
        int pointSize = (int)Math.Round(font.SizeInPoints);
        Font adjustedFont = new Font(fontName, pointSize, style, GraphicsUnit.Point);

        return adjustedFont;
      }
      return null;
    }

    /// <summary>Name of the colour attribute</summary>
    public const string ColourAttribute = "Color";

    /// <summary>
    /// Parses the color of the font.
    /// </summary>
    /// <param name="fontNode">Font node.</param>
    public static Color ParseFontColor(XmlElement fontNode)
    {
      if (fontNode == null)
        throw new ArgumentNullException("fontNode", "fontNode can not be null");
      
      return ParseColor(fontNode.GetAttribute(ColourAttribute));
    }

    /// <summary>
    /// Parses an alignment attribute.
    /// </summary>
    /// <param name="align">Align.</param>
    public static Align ParseAlign(string align)
    {
      if (align == "Right")
        return Align.Right;
      else if (align == "Center")
        return Align.Center;
      else
        return Align.Left;
    }

    internal static void FillFromOptionList(List<string> list, XmlElement parentNode)
    {
      list.Clear();
      XmlElement optionListNode = parentNode["OptionList"];
      if (optionListNode != null)
      {
        foreach (XmlElement optionNode in optionListNode)
        {
          list.Add(optionNode.InnerText);
        }
      }
    }

    #endregion
    #region properties
    private int height;
    /// <summary>
    /// Gets the height of the field in pixels.
    /// </summary>
    public int Height
    {
      get { return height; }
    }

    private int width;
    /// <summary>
    /// Gets the width of the field in pixels.
    /// </summary>
    public int Width
    {
      get { return width; }
    }

    private int top;
    /// <summary>
    /// Gets the top of the field in pixels.
    /// </summary>
    public int Top
    {
      get { return top; }
    }

    private int left;
    /// <summary>
    /// Gets the left of the field in pixels.
    /// </summary>
    public int Left
    {
      get { return left; }
    }

    private string name;
    /// <summary>
    /// Gets the name of the field.
    /// </summary>
    public string Name
    {
      get
      {
        return name;
      }
    }

    private string fieldValue;
    /// <summary>
    /// Gets or sets the value of the field.
    /// </summary>
    public virtual string Value
    {
      get { return fieldValue; }
      set { fieldValue = value; }
    }

    private string hint;
    /// <summary>
    /// Gets the hint for the field.
    /// </summary>
    public virtual string Hint
    {
      get { return hint; }
    }

    private FieldUsage usage;
    /// <summary>
    /// Gets the usage of this field (only applicable in actions).
    /// </summary>
    public FieldUsage Usage
    {
      get { return usage; }
    }

    private bool autoRefill;
    /// <summary>
    /// Gets a value indicating whether altering this field will cause a refill.
    /// </summary>
    /// <value>
    /// 	<c>true</c> if altering this field will cause a refill; otherwise, <c>false</c>.
    /// </value>
    public bool AutoRefill
    {
      get { return autoRefill; }
    }

    private Form form;
    /// <summary>
    /// Gets the form that owns this field.
    /// </summary>
    public Form Form
    {
      get { return form; }
    }

    private string visibilityScript;
    /// <summary>
    /// Gets the JScript script that is evaluated to decide if this field should be shown.
    /// </summary>
    public string VisibilityScript
    {
      get
      {
        return visibilityScript;
      }
    }

    private string clientExtensions;
    /// <summary>
    /// Gets the client extensions for this field.
    /// </summary>
    public string ClientExtensions
    {
      get
      {
        return clientExtensions;
      }
    }

    #endregion
    #region client-side scripts
    
    private ClientScriptEvents eventList;
    /// <summary>
    /// Gets the client script events for this field.
    /// </summary>
    public ClientScriptEvents ClientScriptEvents
    {
      get
      {
        return eventList;
      }
    }

    #endregion
	}
}
